/**
 * Copyright 2019 吉鼎科技.

 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.web.servlet;

import cn.easyplatform.lang.Strings;
import cn.easyplatform.messages.request.BeginRequestMessage;
import cn.easyplatform.messages.request.SimpleRequestMessage;
import cn.easyplatform.messages.request.SimpleTextRequestMessage;
import cn.easyplatform.messages.vos.AuthorizationVo;
import cn.easyplatform.messages.vos.EnvVo;
import cn.easyplatform.messages.vos.LoginVo;
import cn.easyplatform.messages.vos.TaskVo;
import cn.easyplatform.spi.service.ApiService;
import cn.easyplatform.spi.service.ApplicationService;
import cn.easyplatform.spi.service.IdentityService;
import cn.easyplatform.type.*;
import cn.easyplatform.utils.HttpUtil;
import cn.easyplatform.web.WebApps;
import cn.easyplatform.web.contexts.Contexts;
import cn.easyplatform.web.service.ServiceLocator;
import cn.easyplatform.web.utils.ExtUtils;
import cn.easyplatform.web.utils.WebUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: <a href="mailto:hjm@epclouds.com">黄杰敏</a> <br/>
 * @Description: /wxauth/base /wxauth/userinfo
 * @Since: 2.0.0 <br/>
 * @Date: Created in 2020/03/07 09:30
 * @Modified By:
 */
@WebServlet(name = "passportServlet", value = "/passport/*")
public class PassportServlet extends HttpServlet {

    private final static Logger log = LoggerFactory.getLogger(PassportServlet.class);

    private ObjectMapper mapper = new ObjectMapper();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doPost(req, resp);
    }

    /**
     * 输出错误信息
     *
     * @param response
     * @param message
     */
    private void outputError(HttpServletResponse response, String message) {
        response.addHeader("Expires", "0");
        response.addHeader("Cache-Control",
                "no-store, no-cache, must-revalidate");
        response.addHeader("Content-Type", "text/html; charset=utf-8");
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        response.addHeader("Pragma", "no-cache");
        PrintWriter out = null;
        try {
            out = response.getWriter();
            out.write(message);
            out.flush();
        } catch (Exception ex) {
        } finally {
            try {
                out.close();
                out = null;
            } catch (Exception ex) {
            }
        }
    }

    /**
     * 获取项目信息
     *
     * @param rq
     * @return
     */
    private EnvVo getEnv(HttpServletRequest rq, HttpServletResponse rp) {
        String appContext = rq.getParameter("app");
        if (appContext == null)
            appContext = "";
        EnvVo env = new EnvVo();
        env.setAppContext(appContext);
        env.setDeviceType(ExtUtils.getDeviceType(rq));
        IdentityService as = ServiceLocator.lookup(IdentityService.class);
        SimpleRequestMessage req = new SimpleRequestMessage(env);
        IResponseMessage<?> resp = as.getAppEnv(req);
        if (resp.isSuccess()) {
            env = (EnvVo) resp.getBody();
            WebUtils.createAppPath(env);
            rq.getSession().setAttribute(Constants.SESSION_ID, env.getSessionId());
            rq.getSession().setAttribute(
                    Contexts.PLATFORM_APP_ENV, env);
            return env;
        }
        outputError(rp, String.format("errcode:%s,errmsg:%s", resp.getCode(), resp.getBody()));
        return null;
    }

    /**
     * 获取第3方信息
     *
     * @param type
     * @param env
     * @param rp
     * @return
     */
    private Map<String, String> getConfig(String type, EnvVo env, HttpServletResponse rp) {
        ApplicationService as = ServiceLocator.lookup(ApplicationService.class);
        SimpleTextRequestMessage req = new SimpleTextRequestMessage(type);
        req.setSessionId(env.getSessionId());
        IResponseMessage<?> resp = as.getConfig(req);
        if (resp.isSuccess())
            return (Map<String, String>) resp.getBody();
        outputError(rp, String.format("errcode:%s,errmsg:%s", resp.getCode(), resp.getBody()));
        return null;
    }

    /**
     * 执行登陆和注册功能
     *
     * @param req
     * @param resp
     * @param env
     * @param taskId
     * @param userinfo
     * @throws ServletException
     * @throws IOException
     */
    private boolean executeTask(HttpServletRequest req, HttpServletResponse resp, EnvVo env, String taskId, Map<String, Object> userinfo) throws IOException {
        //增加ip地址
        userinfo.put("ip", WebApps.getRemoteAddr(req));
        List<FieldVo> args = new ArrayList<>();
        for (Map.Entry<String, Object> entry : userinfo.entrySet())
            args.add(new FieldVo(entry.getKey(), FieldType.VARCHAR, entry.getValue() == null ? null : entry.getValue().toString()));
        TaskVo to = new TaskVo(taskId);
        to.setVariables(args);
        to.setAgentId("passport");
        if (log.isInfoEnabled())
            log.info("doTask request  {} {} {}", env.getProjectId(), taskId, userinfo);
        ApiService as = ServiceLocator
                .lookup(ApiService.class);
        BeginRequestMessage requestMessage = new BeginRequestMessage(to);
        requestMessage.setSessionId(env.getSessionId());
        IResponseMessage<?> rs = as.task(requestMessage);
        if (rs.isSuccess()) {
            log.info("doTask response {} {} {}", env.getProjectId(), taskId, mapper.writeValueAsString(rs.getBody()));
            req.getSession().setAttribute(Contexts.PLATFORM_APP_ENV, env);
            req.getSession().setAttribute(Constants.SESSION_ID, env.getSessionId());
            if (rs.getBody() instanceof AuthorizationVo) {
                //重定向到主页
                LoginVo vo = new LoginVo();
                vo.setIp(WebApps.getRemoteAddr(req));
                vo.setLocale(req.getLocale().toString());
                req.getSession().setAttribute(Contexts.PLATFORM_USER, vo);
                AuthorizationVo av = (AuthorizationVo) rs.getBody();
                env.setMainPage(av.getMainPage());
                req.getSession().setAttribute(Contexts.PLATFORM_USER_AUTHORIZATION, av);
                resp.sendRedirect(req.getContextPath() + "/main.go");
                return true;
            } else {
                req.getSession().setAttribute("_resp_", rs);
                ApiGatewayServlet.processZul(req, resp);
            }
        } else {
            log.info("doTask response {} {} {}:{}", env.getProjectId(), taskId, rs.getCode(), rs.getBody());
            outputError(resp, String.format("errcode:%s,errmsg:%s", rs.getCode(), rs.getBody()));
        }
        return false;
    }

    /**
     * 微信开放平台的网站登陆
     */
    private void doWeChat(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String code = req.getParameter("code");
        String state = req.getParameter("state");
        if (Strings.isBlank(code) || Strings.isBlank(state)) {
            resp.sendError(404);
        } else {
            EnvVo env = (EnvVo) req.getSession().getAttribute(Contexts.PLATFORM_APP_ENV);
            if (env != null) {
                Map<String, String> tv = getConfig("third-wx", env, resp);
                if (tv != null) {
                    Map<String, String> userBase = getWxUserBaseInfo(tv, code);
                    if (userBase.containsKey("errcode")) {
                        outputError(resp, String.format("errcode:%s,errmsg:%s", userBase.get("errcode"), userBase.get("errmsg")));
                        return;
                    }
                    String openid = userBase.get("openid");
                    String accessToken = userBase.get("access_token");
                    Map<String, Object> userinfo = getWxUserInfo(tv, openid, accessToken);
                    if (userinfo.containsKey("errcode")) {
                        outputError(resp, String.format("errcode:%s,errmsg:%s", userBase.get("errcode"), userBase.get("errmsg")));
                        return;
                    }
                    userinfo.put("authType", "wx");
                    executeTask(req, resp, env, tv.get("entity"), userinfo);
                }//if
            } else {
                resp.sendError(403);
            }
        }//if
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        String url = req.getRequestURI().substring(
                req.getContextPath().length());
        if (url.endsWith("/callback/wx")) {
            doWeChat(req, resp);
        } else if (url.endsWith("/callback/oauth")) {
            doOAuth(req, resp);
        } else if (url.endsWith("/login")) {
            if (req.getParameter("code") == null) {//单点登陆
                doOAuthLogin(req, resp);
            } else if (req.getParameter("type") != null) {//小程序登陆或公众号
                doWxMin(req, resp);
            } else
                resp.sendError(404);
        } else
            resp.sendError(404);
    }

    /**
     * 微信小程序登陆
     */
    private void doWxMin(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String code = req.getParameter("code");
        String app = req.getParameter("app");
        if (app == null)
            app = "";
        Map<String, String> data = new HashMap<>();
        data.put("code", code);
        data.put("app", app);
        data.put("type", "third-wx-" + req.getParameter("type"));
        data.put("ip", WebApps.getRemoteAddr(req));
        data.put("device", DeviceType.MOBILE.getName());
        if (req.getParameter("portlet") != null)
            data.put("portlet", req.getParameter("portlet"));
        if (log.isInfoEnabled())
            log.info("doWxMin request  {} {} {}", app, code, mapper.writeValueAsString(data));
        ApiService as = ServiceLocator
                .lookup(ApiService.class);
        IResponseMessage<?> rs = as.quick(new SimpleRequestMessage(data));
        if (rs.isSuccess()) {
            Object[] result = (Object[]) rs.getBody();
            EnvVo env = (EnvVo) result[0];
            req.getSession().setAttribute(Contexts.PLATFORM_APP_ENV, env);
            req.getSession().setAttribute(Constants.SESSION_ID, ((EnvVo) result[0]).getSessionId());
            LoginVo vo = new LoginVo();
            vo.setIp(WebApps.getRemoteAddr(req));
            vo.setLocale(req.getLocale().toString());
            if (result[1] instanceof AuthorizationVo) {//重定向到主页
                req.getSession().setAttribute(Contexts.PLATFORM_USER_AUTHORIZATION, result[1]);
                req.getSession().setAttribute(Contexts.PLATFORM_USER, vo);
            } else {//未绑定用户
                vo.setType(UserType.TYPE_OAUTH);
                vo.setName("guest");
                req.getSession().setAttribute(Contexts.PLATFORM_USER, vo);
            }
            resp.sendRedirect(req.getContextPath() + "/main.go");
        } else {
            log.info("doWxMin response {} {} {}:{}", app, rs.getCode(), rs.getBody());
            outputError(resp, String.format("errcode:%s,errmsg:%s", rs.getCode(), rs.getBody()));
        }
    }

    /**
     * 单点登陆
     */
    private void doOAuth(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String code = req.getParameter("code");
        String state = req.getParameter("state");
        if (Strings.isBlank(code) || Strings.isBlank(state)) {
            if (req.getParameter("logoutUser") == null) {
                resp.sendError(404);
            } else {
                //退出
                String app = req.getParameter("app");
                if (app == null && req.getParameter("redirect_url") != null) {
                    String url = URLDecoder.decode(req.getParameter("redirect_url"), "utf-8");
                    url = StringUtils.substringAfter(url, "?");
                    String[] parameters = url.split("&");
                    for (String param : parameters) {
                        if (param.startsWith("app")) {
                            app = StringUtils.substringAfter(param, "=");
                            break;
                        }
                    }
                }
                if (app != null)
                    ServiceLocator
                            .lookup(IdentityService.class).logout(new SimpleRequestMessage(new String[]{app, req.getParameter("logoutUser")}));

            }
        } else {
            EnvVo env = (EnvVo) req.getSession().getAttribute(Contexts.PLATFORM_APP_ENV);
            if (env == null)
                env = getEnv(req, resp);
            if (env != null) {
                //获取token
                StringBuilder sb = new StringBuilder();
                sb.append(WebApps.me().getSsoOption("token_url")).append("?client_id=").append(WebApps.me().getSsoOption("client_id"));
                sb.append("&client_secret=").append(WebApps.me().getSsoOption("client_secret")).append("&&grant_type=authorization_code&code=").append(code);
                sb.append("&redirect_uri=").append(WebApps.me().getSsoOption("redirect_url"));
                Map<String, String> data = (Map<String, String>) HttpUtil.post(sb.toString(), null);
                if (data.containsKey("access_token")) {//通过token获取用户信息
                    sb.setLength(0);
                    sb.append(WebApps.me().getSsoOption("user_url")).append("?token=").append(data.get("access_token"));
                    Map<String, Object> userinfo = (Map<String, Object>) HttpUtil.post(sb.toString(), null);
                    if (userinfo.containsKey("error")) {
                        outputError(resp, String.format("errcode:%s,errmsg:%s", userinfo.get("error"), userinfo.get("message")));
                    } else {
                        userinfo.put("authType", "sso");
                        if (executeTask(req, resp, env, WebApps.me().getSsoOption("login_task"), userinfo))
                            env.setLogoutUrl(WebApps.me().getSsoOption("logout_url") + "?userName=" + userinfo.get("username") + "&redirect_url=" + URLEncoder.encode(createSsoLoginUrl(env.getProjectId()), "utf-8"));
                    }
                } else {
                    outputError(resp, String.format("errcode:%s,errmsg:%s", data.get("error"), data.get("message")));
                }
            }
        }
    }

    /**
     * 单点登陆
     *
     * @param req
     * @param resp
     */
    private void doOAuthLogin(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (Strings.isBlank(WebApps.me().getSsoOption("authorize_url"))) {
            resp.sendError(404);
        } else {
            EnvVo env = getEnv(req, resp);
            if (env == null) {
                resp.sendError(403);
                return;
            }
            resp.sendRedirect(createSsoLoginUrl(env.getProjectId()));
        }
    }

    /**
     * 创建单点登陆url
     *
     * @return
     */
    private String createSsoLoginUrl(String app) {
        StringBuilder sb = new StringBuilder(128);
        sb.append(WebApps.me().getSsoOption("authorize_url"));
        sb.append("?client_id=").append(WebApps.me().getSsoOption("client_id"));
        sb.append("&redirect_uri=").append(WebApps.me().getSsoOption("redirect_url"));
        sb.append("&app=").append(app);
        sb.append("&response_type=code&state=").append(RandomStringUtils.random(12, true, true));
        return sb.toString();
    }

    /**
     * @param tv
     * @param code
     * @return {
     * "access_token":"ACCESS_TOKEN",
     * "expires_in":7200,
     * "refresh_token":"REFRESH_TOKEN",
     * "openid":"OPENID",
     * "scope":"SCOPE"    :snsapi_base snsapi_userinfo
     * }
     */
    private Map<String, String> getWxUserBaseInfo(Map<String, String> tv, String code) {
        StringBuilder sb = new StringBuilder(100);
        sb.append("https://api.weixin.qq.com/sns/oauth2/access_token?appid=").append(tv.get("appid")).append("&secret=").append(tv.get("appsecret")).append("&code=").append(code).append("&grant_type=authorization_code");
        Map<String, String> data = null;
        try {
            data = (Map<String, String>) HttpUtil.get(sb.toString());
            log.debug("getWxUserInfo.result={}", data);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            data = new HashMap<String, String>();
            data.put("errcode", "10000");
            data.put("errmsg", e.getMessage());
        }
        return data;
    }

    /**
     * @param user_access_token
     * @param openId
     * @return {
     * "openid":" OPENID",
     * "nickname": NICKNAME,
     * "sex":"1",
     * "province":"PROVINCE",
     * "city":"CITY",
     * "country":"COUNTRY",
     * "headimgurl":       "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
     * "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
     * "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
     * }
     */
    private Map<String, Object> getWxUserInfo(Map<String, String> tv, String openId, String user_access_token) {
        Map<String, Object> data = null;
        try {
            StringBuilder sb = new StringBuilder(100);
            sb.append("https://api.weixin.qq.com/sns/userinfo?access_token=").append(user_access_token).append("&openid=").append(openId).append("&lang=zh_CN");
            data = (Map<String, Object>) HttpUtil.get(sb.toString());
            log.debug("getWxUserInfo.result={}", data);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            data.put("errcode", "10000");
            data.put("errmsg", e.getMessage());
        }
        return data;
    }

}



